import java.util.*;
import java.awt.*;
import java.io.IOException;

public class PlayerModel implements Player {
	
	private TablePanel tablepanel;
	private PlayerData playerData;
	private Connection connection;
	private Model model;
	
	private Group selectedCards;
	private Point prevPoint;
	
	private ArrayList<Group> groups = new ArrayList<Group>();
	private Map<Card,Group> cardgroup = new HashMap<Card,Group>();

	
	public PlayerModel(PlayerData pd, Model model) {
		playerData = pd;
		playerData.setPlayerModel(this);
		this.model = model;
		selectedCards = new Group();
	}
	
	public PlayerModel(PlayerData pd, PlayerListener listener, Connection connection, Model model) {
		this(pd,connection, model);
		setPlayerListener(listener);
	}
	
	public PlayerModel(PlayerData pd, Connection connection, Model model) {
		this(pd, model);
		this.connection = connection;
	}
	
	public void setTablePanel(TablePanel tp) {
		this.tablepanel = tp;
		alert();
	}
	
	public void updateView() {
		Dbg.logln("PlayerModel.updateView");
		if (tablepanel!=null) {
			tablepanel.repaint();
		}
	}
	
	public String getName() {
		return "default name";
	}
	
	public void setPlayerListener(PlayerData pl) {
		this.playerData = pl;
		rebuildGroups();
	}
	
	public void alert(){
		Dbg.logln("PlayerModel.alert");
		rebuildGroups();
		updateView();
	}
	
	public void sendInfoAcrossServer() {
		try {
			connection.syncToServer();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NullPointerException e) {
			//e.printStackTrace();
		}
	}
	
	private void rebuildGroups() {
		groups = new ArrayList<Group>();
		cardgroup = new HashMap<Card,Group>();
		for (Card c: getCards()) {
			ArrayList<Group> overlapgroups = new ArrayList<Group>();	// list of groups this card overlaps with
			for(Card other: cardgroup.keySet()) {
				if (c.overlaps(other)) {
					Group g = cardgroup.get(other);
					if(!overlapgroups.contains(g)) {
						overlapgroups.add(g);
					}
				}
			}
			Group mergegroup = new Group(c);	// new group, that has the new card in it
			for(Group g: overlapgroups) {
				mergegroup.addAll(g);
				groups.remove(g);
			}
			groups.add(mergegroup);
			for(Card cd: mergegroup) {
				cardgroup.put(cd,mergegroup);
			}
		}
	}
	
	public Collection<Group> getGroups() {
		return groups;
	}
	
	
	/**
	* Set a {@link Card} as the only selected card.
	* 
	* @param card The <code>Card</code> to be selected.
	*/
	public boolean selectSingleCard(Card card) /*throws IllegalArgumentException*/ {
	System.out.println("SSC");
		deselectAllCards();
		boolean val = selectCard(card);
		playerData.select(selectedCards);
		
		return val;
	}
	
	/**
	* Add a {@link Card} to those selected.
	* 
	* @param card The <code>Card</code> to be selected.
	*/
	public boolean selectCard(Card card) /*throws IllegalArgumentException*/ {
		System.out.println("SC");
		
		if (card == null) {
			return false;
		}
		
		Dbg.logln("selectCard - " + card.toString());
		
		selectedCards.add(card);
		playerData.select(selectedCards);
	
		return false;
	}
	
	/**
	Replaces the current selected cards with the given set of cards
	*/
	public boolean selectCards(Set<Card> cards) /*throws IllegalArgumentException*/ {
		System.out.println("SCS");
		
		if (cards == null) {
			return false;
		}
		
		selectedCards = new Group(cards);
		playerData.select(selectedCards);
		
		return false;
	}
	
	/**
	 * Sends selected cards to the server
	 * @param ids
	 */
	public void sendSelect(int[] ids) {
		String args = "";
		
		//deselect all cards
		if (ids == null) {
			try {
				connection.sendCommand(Commands.CMD_DESELECT_ALL, null);
				return;
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		for(int id: ids) {
			args += id + Commands.DEL;
		}
		
		try {
			connection.sendCommand(Commands.CMD_SELECT, args);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendDeselect () {
		try {
			connection.sendCommand(Commands.CMD_DESELECT_ALL, null);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void sendMoveBy(Point point) {
		try {
			connection.sendCommand(Commands.CMD_MOVE_BY, point.x + Commands.DEL + point.y);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/**
	* Remove a {@link Card} from those selected.
	* 
	* @param card The <code>Card</code> to be deselected.
	*/
	public void deselectCard(Card card) {
		if (card==null) {
			// uncomment the following line once exception handling is implemented in the View
			//throw IllegalArgumentException("card cannot be null");
		}
		selectedCards.remove(card);
		playerData.select(selectedCards);
	}
	
	public void toggleSelectCard(Card card) {
		if (card==null) {
			// uncomment the following line once exception handling is implemented in the View
			//throw IllegalArgumentException("card cannot be null");
		}
		if (isSelectedCard(card)) {
			deselectCard(card);
		} else {
			selectCard(card);
		}
		playerData.select(selectedCards);
	}
	
	
	public void deselectAllCards() {
		selectedCards = new Group();
		playerData.select(selectedCards);
		
		//send to server
		sendSelect(null);
	}
	
	/**
	 * Locally deselects all cards. Does not send to server.
	 */
	public void deselectAllCardsLocal() {
		selectedCards = new Group();
		playerData.select(selectedCards);
		sendSelect(null);
	}
	
	/**
	 * Flips the currently selected {@link Card}.
	 * 
	 */
	public void flipSelected(){
		
		Dbg.logln("Flip - " + selectedCards.toString());
		playerData.flip();
		
		//send flip to server
		try {
			connection.sendCommand(Commands.CMD_FLIP, null);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void shuffleSelected(){
		playerData.shuffle();
		alert();
		
	}
	
	/**
	 * Tests if a {@link Card} is currently selected.
	 * 
	 * @param card The <code>Card</code> to test.
	 * @return <code>true</code> if the specified <code>Card</code> is selected, otherwise <code>false</code>.
	 */
	public boolean isSelectedCard(Card card) {
		return selectedCards.contains(card);
	}
	
	/**
	 * Returns the currently selected {@link Card}.
	 * 
	 * @return the currently selected <code>Card</code>.
	 */
/*	public Card getSelected(){
		return selectedCard;
	}*/
	
	
	/**
	 * Records the starting <code>Point</code> of a mouse drag. Moves selected {@link Card} to the top of any cards it is touching. 
	 * 
	 * @param point The starting location of the drag.
	 */
	public void dragStart(Point point) {
		prevPoint = point;
		playerData.hold();
	}
	
	/**
	 * Called while the mouse is being dragged to update the position and Z-value of the selected {@link Card}.
	 * 
	 * @param point The current location of the mouse.
	 */
	public boolean dragContinue(Point point) {
		int dx = point.x - prevPoint.x;
		int dy = point.y - prevPoint.y;
		prevPoint = point;

		return playerData.moveBy(new Point(dx,dy));

	}
	
	/**
	 * Ends a drag??
	 * 
	 * @param point **NOT USED**
	 */
	public void dragEnd(Point point) {
		playerData.release();
	}
	
	
	/**
	* Returns a List of Cards.
	*/
	public java.util.List<Card> getCards() {
		
		if (playerData != null) {
			return model.getRoom().getTable().getDeck().getDeck();
		} else {
			return new ArrayList<Card>();
		}
	}
	
	public Set<Card> getSelectedCards() {
		return selectedCards;
	}
	
	public boolean isSelected() {
		return !selectedCards.isEmpty();
	}
	

	public void setTableSize(Dimension d){
		playerData.getTable().setSize(d);
	}
	
	public Dimension getTableSize(){
		return playerData.getTable().getSize();
	}
	

	public void setSnap(boolean snap) {
		playerData.setSnap(snap);
	}

	/**
	 * @return the connection
	 */
	public Connection getConnection() {
		return connection;
	}

	/**
	 * @return the model
	 */
	public Model getModel() {
		return model;
	}
	
	public void setPlayerListener(PlayerListener pl) {
		// TODO Auto-generated method stub
		
	}
	
	public void serverMoveBy(Set<Card> cards, int x, int y){
		for(Card c: cards){
			c.setLocation(c.getLocation().x + x, c.getLocation().y + y);
			c.setZ(playerData.findHighestZ(c)+1);
		}
	}

	public void syncFromServer() throws IOException {
		connection.sendCommand(Commands.CMD_SYNC, null);
	}
	
	public void sendToBack() {
		playerData.sendToBack();
	}
}
